import pygame, time
from pygame.locals import *
import pygame.locals as pygamelocals
import random

pygame.font.init()


# EVENT TYPES - changes should be made to event_names dict in gamebaker.py too
EVENT_INIT = 1
EVENT_KEYDOWN = 2 # multiply by key value so all even nos are taken
EVENT_KEYUP = -2 # multiply by key value so all -ve even nos are taken
EVENT_ITERATION = 3
EVENT_LMOUSEUP = 5
EVENT_RMOUSEUP = 7
EVENT_OFFSCREEN = 9


# INSTRUCTIONS
ACTION_SET_VALUE = 0
ACTION_MOVE_X = 1
ACTION_MOVE_Y = 2
ACTION_CHANGE_WORKSTATE = 3
ACTION_CHANGE_GAMESCREEN = 4

#"""
class game_event(Exception):
    def __init__(self,instructions):
        self.instruction = instructions
#shouldn't be required any more
#"""

class gb_runtime_exception(Exception):
    def __init__ (self,log,orig_except=None):
        self.log = str(log)
        self.orig_except=orig_except
    def __str__(self):
        return self.log



def run_game(game):
    """run a general game
    - a bit messy, some time I will make it more OO
    """

    myscreen = Screen((800,600),game.title)


    def get_game_screen(name,game,scriptvars,screen,saved={},scriptglobals={}):
        if saved.has_key(name):
            return saved[name]
        else:
            return RunGameScreen(screen,game,game.gamescreens[name],scriptvars,scriptglobals)

    scriptvar = scriptgame(game)
    pygame.init()

    # Run Game init code to import modules etc.
    scriptglobals={}
    # TODO:
    # From Release 0.2 onward we can remove the except - left in to
    # retain compatibility with previous 0.1.x releases' games
    try:
        if not game.initcode == "":
            exec game.initcode in {}, scriptglobals
    except: pass

    print scriptglobals
    
    #Check if a gamescreen is already loaded, otherwise return a new Rungamescreen object
    savedgamescreens = {}
    try:
        rungamescreen = get_game_screen(game.startscreen, game, \
                                        scriptvar, myscreen, savedgamescreens,scriptglobals=scriptglobals)
    except Exception,inst:
        pygame.quit()
        raise gb_runtime_exception(log="Error Finding Initial GameScreen - " \
                    + str(inst) ,)

    currentgamescreen = game.startscreen



    while 1:
        try:
            rungamescreen.gameloop()
            # TODO
            # Should replace time.sleep with pygame function, but the call need to be tested
            # at runtime to ensure it works (not all systems respond correctly)
            # If it doesn't work try the other call, or time.sleep
            time.sleep(0.04)

        # Catch events that have to be processed at this level
        except game_event,inst:
                command = inst.instruction[0]
                params = inst.instruction[1:]
                if command == "changeGameScreen":
                    gsname = params[0]
                    savestate = params[1]
                    if savestate:
                        savedgamescreens[currentgamescreen] = rungamescreen
                    else:
                        del savedgamescreens[currentgamescreen]
                    currentgamescreen = gsname
                    rungamescreen = get_game_screen(gsname,game,scriptvar,myscreen,savedgamescreens,scriptglobals=scriptglobals)

        except Exception, inst:
            pygame.quit()
            raise gb_runtime_exception(log=str(inst))
    pygame.quit()




class Screen(object):
    """"Screen/window Class for rendering onto."""
    def __init__(self,size,name):
        self.surface = pygame.display.set_mode(size)
        pygame.mouse.set_visible(1)
        self.surface.fill((255,255,255))
        pygame.display.set_caption(name)
        self.resolution = size

    def clear(self,color=(255,255,255)):
        self.surface.fill(color)

    def render(self):
        pygame.display.update()

class RunGameScreen(object):
    """"The Class that represents an individual gamescreen (or level etc) during execution"""
    def __init__(self,screen,game,gamescreen,scriptgamevar,scriptglobals):
        self.game = game
        self.gamescreen = gamescreen # the game.gamescreen object
        self.screen = screen 
        self.surface = pygame.Surface(self.gamescreen.dimensions)
        self.scriptglobals = scriptglobals

        self.scriptgamevar = scriptgamevar # "game" object for scripting

        self.spriteobjects = {} # Loaded (runtime) sprite instances
        for go in gamescreen.startobjects:
            if go.sprite is not None:
                if not self.spriteobjects.has_key(go.sprite):
                    self.spriteobjects[go.sprite] = RunSprite(self.game.sprites[go.sprite])


        # Order is important:
        # 1
        self.gameobjects = {}

        # 2
        self.scriptgamescreenvar = scriptgamescreen(self)
        
        # 3
        # HAVE TO CHANGE THIS - how to name objects?
        for a in gamescreen.startobjects:
            self.gameobjects[a.name] = RunGameObject(self,a)




    def gameloop(self):
        """Main Game Loop - called each itteration"""
        self.input() 
        self.itteration()
        # Draw this gamescreen on to our window.
        self.render()
        # blit the window on to the monitor.
        self.screen.render()

        events = pygame.event.get()
        game_objects = self.gameobjects.values()
        for event in events:
            if event.type == QUIT:
                # Hack - simple solution
                raise Exception
            elif event.type == KEYDOWN:
                for gameobj in game_objects:
                    gameobj._do_event(EVENT_KEYDOWN)
                    gameobj._do_event(EVENT_KEYDOWN*event.key)
                pass
            elif event.type == KEYUP:
                for gameobj in game_objects:
                    gameobj._do_event(EVENT_KEYUP)
                    gameobj._do_event(EVENT_KEYUP*event.key)
                pass
            elif event.type == MOUSEBUTTONUP:
                (x,y) = pygame.mouse.get_pos()
                x = x - self.scriptgamescreenvar.x
                y = y - self.scriptgamescreenvar.y
                for gameobj in game_objects:
                    if gameobj.check_point_collision(x,y):
                        if event.button == 1:
                                gameobj._do_event(EVENT_LMOUSEUP)
                        elif event.button == 2:
                                gameobj._do_event(EVENT_RMOUSEUP)

            

    def render(self):
        """render a frame"""
        # Draw background - to be replaced with options (background image, colour etc)
        try:
            self.screen.clear(self.scriptgamescreenvar.color)
        except :
            self.screen.clear()


        # Draw objects - blit only part on screen
        swidth,sheight = self.screen.resolution
        gswidth,gsheight = self.gamescreen.dimensions

        #------------------------------------------#
        # NB: use the .z attribute of the game objects
        # to order the rendering.
        #------------------------------------------#

        gameobjs = {}
        gos = self.gameobjects.values()
        for i in range(len(gos)):            
            gameobjs.setdefault(gos[i].scriptvar.z,[ ]).append(gos[i])

        zvals = gameobjs.keys()[:]
        zvals.sort()

        for i in zvals:
            objs = gameobjs[i]
            for item in objs:

                if item.scriptvar.x - self.scriptgamescreenvar.x + item.surface.get_width() < 0 or  \
                    item.scriptvar.x - self.scriptgamescreenvar.x > swidth or \
                    item.scriptvar.y - self.scriptgamescreenvar.y + item.surface.get_height()< 0 or \
                    item.scriptvar.y - self.scriptgamescreenvar.y  > sheight:

                    item._do_event(EVENT_OFFSCREEN)
                else:
                    self.screen.surface.blit(item.surface,(item.scriptvar.x - \
                    self.scriptgamescreenvar.x,item.scriptvar.y - self.scriptgamescreenvar.y))


    def input(self):
        """Completely not done yet"""
        pass


    def itteration(self):
        """Called every itteration"""
        for item in self.gameobjects.values():
            # standard stuff (e.g. sprite frames)
            item._dostep()
            # non-standard iteration handler
            item._do_event(EVENT_ITERATION)
        pass




class RunGameObject(object):
    """"The Class that represents a GameObject while the game is being executed"""
    def __init__(self,gamescreenobj,GameObj):

        # Object for scripting
        self.scriptvar = scriptgameobj(self)
        # Set up sprite
        self.scriptvar.sprite = GameObj.sprite
        self.scriptvar.frame = 0
        self.scriptvar.__loopcounter = 0

        self.scriptvar.workstate = GameObj.baseworkstate
        self.oldworkstate = GameObj.baseworkstate

        self.game = gamescreenobj.game
        self.gamescreenobj = gamescreenobj
        self.GameObject = GameObj
        self.surface = pygame.Surface((10,10))

        # call the init event
        self._do_event(EVENT_INIT)




    def _do_event(self,event,scriptlocals = None):
        """ call a scripted event. 
        Scriptlocals should normally be None
        """
        ws = self.game.workstates[self.scriptvar.workstate]
        event_code = ws.actions.get(event,"")

        # Walk the inheritance tree and evaluate the method.
        while event_code == "" and ws.inherits is not None:
            ws = self.get(self.game[ws.inherits])
            eventcode = self.workstate.actions.get(event,"")

        if event_code == "":
            return
        
        if scriptlocals is None:
            scriptlocals = {"self":self.scriptvar,"game": self.gamescreenobj.scriptgamevar, \
                            "gamescreen":self.gamescreenobj.scriptgamescreenvar, \
                            "random":random,"constants":pygamelocals}

        print self.gamescreenobj.scriptglobals

        exec event_code in self.gamescreenobj.scriptglobals, scriptlocals
        


        

    def check_point_collision(self,x,y):
        """Checks if a point is within the object's collision area"""
        (width,height) = self.surface.get_size()
        return self.scriptvar.x <= x and self.scriptvar.y <= y and self.scriptvar.x + width >= x and self.scriptvar.y + height >= y

    def drawsprite(self,sprite,x,y,frame = None):
        """draws a sprite on the object at position x,y """

        spr = self.gamescreenobj.spriteobjects.get(sprite,None)
        if spr is None:
            # Load sprite
            self.gamescreenobj.spriteobjects[sprite] =  \
                    RunSprite(self.gamescreenobj.game.sprites[sprite])
        spr = self.gamescreenobj.spriteobjects[sprite]

        if frame is None:
            self.surface.blit(spr.frames[self.scriptvar.frame],(x,y))
        else:
            self.surface.blit(spr.frames[frame],(x,y))


    def drawtext(self,text,size=None,colour=None):
        """ Using "Colour" since that is the correct English spelling ;-)
        Also not using defaults so we can overload the function
        to allow different calling conventions later"""

        if not type(text) == type(""):
            text = str(text)
        if size is None:
            size = 12
        if colour is None:
            colour = (0,0,0)

        # Create a font
        font = pygame.font.Font(None, size)

        # Render the text
        text = font.render(text, True, colour)#, background colour

        # Create a rectangle
        textRect = text.get_rect()

        # Center the rectangle
        textRect.centerx = self.surface.get_rect().centerx
        textRect.centery = self.surface.get_rect().centery

        # Blit the text
        self.surface.blit(text, textRect)

    def clear(self):
        if self.scriptvar.sprite is None:
            self.surface.fill(self.scriptvar.color)

    def resize(self,res,height=None):
        if not height is None:
            res = (res,height)
        self.surface = pygame.transform.scale(self.surface,res)
        self.scriptvar.width = res[0]
        self.scriptvar.height = res[1]

    def _dostep(self):
        # Check if we have changed workstate:
        if not self.oldworkstate == self.scriptvar.workstate:
            self._do_event(EVENT_INIT)
            self.oldworkstate = self.scriptvar.workstate
        if not self.scriptvar.sprite is None:
            spr = self.gamescreenobj.spriteobjects.get(self.scriptvar.sprite,None)
            if spr is None:
                # Load sprite
                self.gamescreenobj.spriteobjects[self.scriptvar.sprite] =  \
                        RunSprite(self.gamescreenobj.game.sprites[self.scriptvar.sprite])
            spr = self.gamescreenobj.spriteobjects.get(self.scriptvar.sprite,None)

            self.surface.set_colorkey(spr.alphakey)
            framerate = spr.framerate

            self.scriptvar.__loopcounter = (self.scriptvar.__loopcounter + 1)  \
                                                % (framerate*len(spr.frames))

            self.scriptvar.frame = (self.scriptvar.__loopcounter ) / framerate

            self.surface.blit(spr.frames[self.scriptvar.frame],
                                (0,0))


class RunSprite(object):
    """"The Class that represents a Sprite while the game is being executed"""
    def __init__(self,sprite):
        self.sprite = sprite
        self.framerate = sprite.framerate
        self.frames = []

        # Set transparent colour based on sprite instance
        self.alphakey = sprite.alphakey

        # Now load the raw images into surfaces
        temp = {}
        for im in sprite.imagefiles:
            if not temp.has_key(im["filename"]):
                surface = pygame.image.load(im["filename"])
                print im["filename"]
                temp[im["filename"]] = surface
                
        # And save sub-surfaces for each frame (effectively a pointer to pixelarray, so memory efficient)
        for im in sprite.imagefiles:
            x1,y1 = im["topleft"]
            x2,y2 = im["bottomright"]
            rec = pygame.Rect( x1 , y1 , x2 - x1 , y2 - y1 )
            self.frames.append(temp[im["filename"]].subsurface(rec))



""" 
******************************************
script objects:
******************************************
These are the objects that the user interacts with in scripts.
"""

class scriptobject(object):
    def __init__():

        pass

class scriptgame(scriptobject):
    """The object used in scripting language for a *game*
        - all in-game data should be stored here so 
        games can be saved easily"""
    def __init__(self,game):
        self.vars = {}
        self.score = 0
        pass
    def change_game_screen(self,gs,savestate=False):
        raise game_event(["changeGameScreen",gs,savestate])



class scriptgamescreen(scriptobject):
    """The object used in scripting language for a *game screen*
        - all in-game data should be stored here so 
        games can be saved easily"""
    def __init__(self,gamescreen):
        self.vars = {}
        self.__gameobjects = gamescreen.gameobjects
        self.__gamescreen = gamescreen
        self._vx,self._vy,self.y,self.x = 0,0,0,0
        self.width = self.__gamescreen.gamescreen.dimensions[0]
        self.height = self.__gamescreen.gamescreen.dimensions[1]
        try:
            self.color = gamescreen.gamescreen.color
        except: pass

    def get_object(self,name):
        """Returns the gameobject script interfaces for active game objects"""
        return self.__gameobjects[name].scriptvar

    def move_gamescreen(self,target,gradual=False,maxspeed=10,acceleration=2):
        if gradual:
            if self.x + 400 < target[0]:
                if self._vx < maxspeed:
                    self._vx += acceleration
            else:
                if self._vx > - maxspeed:
                    self._vx -=acceleration
            if self.y + 300 < target[1]:
                if self._vy < maxspeed:
                    self._vy += acceleration
            else:
                if self._vy > -maxspeed:
                    self._vy -= acceleration

        if abs(self._vx) > abs(self.x + 400 - target[0]):
            gradual = False
        if abs(self._vy) > abs(self.y + 300 - target[1]):
            gradual = False

        if not gradual:
            # TODO make this vary with screen resolution
            self._vx =  target[0] - self.x - 400
            self._vy =  target[1] - self.y - 300



        self.x = self.x + self._vx
        self.y = self.y + self._vy

        if self.x < 0: self.x = 0
        if self.y < 0: self.y = 0
        if self.x > self.__gamescreen.gamescreen.dimensions[0] - 800:
            self.x = self.__gamescreen.gamescreen.dimensions[0] - 800
        if self.y > self.__gamescreen.gamescreen.dimensions[1] - 600:
            self.y = self.__gamescreen.gamescreen.dimensions[1] - 600



    def check_collisions(self,obj1,obj2):
        """Checks a collision between objects
           You can also provide lists of game objects to check"""
        if type(obj1) == list and type(obj2) == list:
            for o1 in obj1:
                for o2 in obj2:
                    if self.check_collisions(o1,o2):
                        return True
            return False

        if type(obj1) == list:
            for o1 in obj1:
                if self.check_collisions(o1,obj2):
                    return True
            return False

        if type(obj2) == list:
            for o2 in obj2:
                if self.check_collisions(obj1,o2):
                    return True
            return False
            
        o1 = self.__gamescreen.gameobjects[obj1]
        o2 = self.__gamescreen.gameobjects[obj2]

        w1 = o1.surface.get_width()
        w2 = o2.surface.get_width()

        h1 = o1.surface.get_height()
        h2 = o2.surface.get_height()

        x1 = o1.scriptvar.x
        x2 = o2.scriptvar.x

        y1 = o1.scriptvar.y
        y2 = o2.scriptvar.y

        if x1 < x2 + w2:
            if x1 + w1> x2:
                if y1 < y2 + h2:
                    if y1+ h1 > y2:
                        return True
                else:
                    if y2 > y1 + h1:
                        return True
        else:
            if x2 + w2 > x1:
                if y1< y2 + h2:
                    if y1 + h1 > y2:
                        print "1"
                        return True
                else:
                    if y2 < y1 + h1:
                        return True
        return False



class scriptgameobj(scriptobject):
    """The object used in scripting language for a *game object*
        - all in-game data should be stored here so 
        games can be saved easily"""
    def __init__(self,go):
        self.vars = {}
        self.draw_text = go.drawtext
        self.clear = go.clear
        self.resize = go.resize
        self.color=(0,0,0)
        self.draw_sprite = go.drawsprite
        self.z=0
        pass

    def move_velocity(self):
        # Move object with a velocity
        self.x += self.vx
        self.y += self.vy





